// Program: ata
// Description: Creates all ATA compatible block devices.
//

//
// Ata.cc -- Defines the interface for a device representing an ATA disk.
//

#include "AtaDevice.h"
#include <stdio.h>
#include <string.h>

AtaDevice::AtaDevice(u16int commandBase, u16int controlBase, u8int drive) :
    commandBase(commandBase), controlBase(controlBase), drive(drive), currentRequest(NULL)
{
  // Spin up the device.
  outb(commandBase+6, 0x00);
  
  // Identify
  identify();
  
  // Read partition table.
  unsigned char buffer[512];
  readSectorBlock(0x0, buffer);
  // Partition table starts at 0x1be and is 64 bytes long.
  memcpy((u8int*)&physPartitions, (u8int*)(((u32int)buffer)+0x1be), 64);
  
  resource = createResource((Callable*)this);
  addFunction(resource, (Resource::Call4)&AtaDevice::requestRead,  FILE_READ);
  addFunction(resource, (Resource::Call4)&AtaDevice::requestWrite, FILE_WRITE);
}

u32int AtaDevice::requestRead(uint impl, uint offset, uint size, u8int *buf)
{
  if (impl > 3) // Only four partitions exist - we totally ignore extended ones.
    return -1;
  
  // Does this request currently exist?
  Request *req = requestMap.lookup((uint)buf);
  if (req)
  {
    // OK then, is it completed?
    if (req->completed)
    {
      // Cache the size, delete it and return the size.
      uint size = req->size;
      requestMap.remove((uint)buf);
      delete req;
      return size;
    }
    else
    {
      // No, so just return -1. (the "keep spinning" signal)
      return (uint)-1;
    }
  }
  else
  {
    // It needs to be added.
    // impl can either be -1, or 1..whatever. So if it is >0, it needs decrementing.
    if (impl > 0)
      impl--;
    req = new Request(offset, size, buf, this, impl, false);
    requestMap.add((uint)buf, req);
    requestQueue.insertLast(req);
    
    // If we aren't doing anything at the moment...
    if (!currentRequest)
      dispatchRequest(); // ... Kick us into action.
    
    // Tell the caller it's not ready yet.
    return (uint)-1;
  }
}

u32int AtaDevice::requestWrite(uint impl, uint offset, uint size, u8int *buf)
{
  if (impl > 3) // Only four partitions exist - we totally ignore extended ones.
    return -1;
  
  // Does this request currently exist?
  Request *req = requestMap.lookup((uint)buf);
  if (req)
  {
    // OK then, is it completed?
    if (req->completed)
    {
      // Cache the size, delete it and return the size.
      uint size = req->size;
      requestMap.remove((uint)buf);
      delete req;
      return size;
    }
    else
    {
      // No, so just return -1. (the "keep spinning" signal)
      return (uint)-1;
    }
  }
  else
  {
    // It needs to be added.
    // impl can either be -1, or 1..whatever. So if it is >0, it needs decrementing.
    if (impl > 0)
      impl--;
    req = new Request(offset, size, buf, this, impl, true);
    requestMap.add((uint)buf, req);
    requestQueue.insertLast(req);
    
    // If we aren't doing anything at the moment...
    if (!currentRequest)
      dispatchRequest(); // ... Kick us into action.
    
    // Tell the caller it's not ready yet.
    return (uint)-1;
  }
}

void AtaDevice::deviceReady()
{
  if (currentRequest)
  {
    if (!currentRequest->isWrite)
    {
      currentRequest->disk->readBuffer(currentRequest->size,
                                       currentRequest->buffer);
    }
    else
    {
      // writeBuffer...
    }
    
    // Current request is completed.
    currentRequest->completed = true;
    
    // next request, please!
    dispatchRequest();
  }
}

void AtaDevice::dispatchRequest()
{
  currentRequest = requestQueue.removeFirst();
  if (currentRequest)
  {
    u32int start_lba = 0;
    if (currentRequest->partition != -1)
      start_lba = getPartitionInfo(currentRequest->partition).start_lba;

    if (currentRequest->isWrite)
    {
    
    }
    else
    {
      readSectorNonBlock(start_lba + currentRequest->address/512,
                         currentRequest->size/512+1);
    }
  }
}

void AtaDevice::identify()
{
  unsigned char state = inb(0x1f7);
  outb(commandBase+7, 0xec); // Send IDENTIFY DEVICE command.

  int i = 0;
  state = inb(0x1f7);
  do
  {
    unsigned char newstate = inb(0x1f7);
    if(newstate != state)
      state = newstate;
  }
  while((state & HD_STATUS_BUFFER_NEEDS_SERVICING) == 0);

  for(i = 0; i < 256; i++)
    ident[i] = inw(0x1f0);
}

void AtaDevice::getModelName(char *buf, int maxLen)
{
  if (maxLen < 41)
    return;
  for (int i = 0; i < 20; i++)
  {
    buf[i*2]  = ident[0x1B+i] >> 8;
    buf[i*2+1] = ident[0x1B+i] & 0xFF;
  }
  for (int i = 39; i > 0; i--)
    if (buf[i] != ' ')
      break;
    else
      buf[i] = '\0';
  buf[40] = '\0';
}

void AtaDevice::getSerialNumber(char *buf, int maxLen)
{
  if (maxLen < 21)
    return;
  for (int i = 0; i < 10; i++)
  {
    buf[i*2]  = ident[0x0A+i] >> 8;
    buf[i*2+1]= ident[0x0A+i] & 0xFF;
  }
  for (int i = 20; i > 0; i--)
    if (buf[i] != ' ')
      break;
  else
    buf[i] = '\0';
}

void AtaDevice::getFirmwareRevision(char *buf, int maxLen)
{
  if (maxLen < 9)
    return;
  for (int i = 0; i < 4; i++)
  {
    buf[i*2]  = ident[0x17+i] >> 8;
    buf[i*2+1]= ident[0x17+i] & 0xFF;
  }
  for (int i = 8; i > 0; i--)
    if (buf[i] != ' ')
      break;
  else
    buf[i] = '\0';
}

u32int AtaDevice::getCylinders()
{
  return (u32int)ident[0x01];
}

void AtaDevice::readSectorBlock(u32int lba, u8int *buf)
{
  // get the initial state of the drive.
  unsigned char state = inb(commandBase+7);
  while (!(state & HD_STATUS_READY))
    state = inb(commandBase+7);
  
  unsigned char drivereg = 0x00;
  drivereg |= 0x80; // bit 7 = 1
  drivereg |= 0x20; // bit 5 = 1
  drivereg |= 0x40; // bit 6 = 1 = LBA mode.
  if(drive == 1) drivereg |= 0x10; // drive select

  // LBA mode: 4 byte lba address.
  // lowest byte => sector n register
  unsigned char sector = (unsigned char) (lba & 0x000000FF);
  // next byte => cyl low
  unsigned char cyl_low = (unsigned char) ((lba >> 8) & 0x000000FF);
  // next byte => cyl hi
  unsigned char cyl_hi = (unsigned char) ((lba >> 16) & 0x000000FF);
  // most sig byte => least sig 4 bits into head number
  drivereg |= (unsigned char) ((lba >> 24) & 0x0000000F);

  // sector count = 1
  outb(commandBase+2, 0x1);
  // sector number
  outb(commandBase+3, sector);
  // cylinder low
  outb(commandBase+4, cyl_low);
  // cylinder high
  outb(commandBase+5, cyl_hi);
  // drive/head register
  outb(commandBase+6, drivereg);
  // command register
  outb(commandBase+7, 0x20); // 'read sectors with retry'

  state = inb(commandBase+7);
  while (!(state & HD_STATUS_READY))
    state = inb(commandBase+7);
  
  u16int *sbuf = (u16int*)buf;
  for (int i = 0; i < 256; i++)
    sbuf[i] = inw(commandBase);

}

/**
  Read a number of sectors on disk, activating interrupts and not
  blocking.
**/
void AtaDevice::readSectorNonBlock(u32int lba, u8int nSectors)
{
  // get the initial state of the drive.
  unsigned char state = inb(commandBase+7);
  while (!(state & HD_STATUS_READY))
    state = inb(commandBase+7);
  
  unsigned char drivereg = 0x00;
  drivereg |= 0x80; // bit 7 = 1
  drivereg |= 0x20; // bit 5 = 1
  drivereg |= 0x40; // bit 6 = 1 = LBA mode.
  if(drive == 1) drivereg |= 0x10; // drive select

  // LBA mode: 4 byte lba address.
  // lowest byte => sector n register
  unsigned char sector = (unsigned char) (lba & 0x000000FF);
  // next byte => cyl low
  unsigned char cyl_low = (unsigned char) ((lba >> 8) & 0x000000FF);
  // next byte => cyl hi
  unsigned char cyl_hi = (unsigned char) ((lba >> 16) & 0x000000FF);
  // most sig byte => least sig 4 bits into head number
  drivereg |= (unsigned char) ((lba >> 24) & 0x0000000F);

  // sector count
  outb(commandBase+2, nSectors);
  // sector number
  outb(commandBase+3, sector);
  // cylinder low
  outb(commandBase+4, cyl_low);
  // cylinder high
  outb(commandBase+5, cyl_hi);
  // drive/head register
  outb(commandBase+6, drivereg);
  // Enable interrupts.
//   u8int reg = inb(controlBase+6);
  outb(controlBase+6, 0x08);
  
  while (!(state & HD_STATUS_READY))
    state = inb(commandBase+7);
  
  // command register
  outb(commandBase+7, 0x20); // 'read sectors with retry'
}

/**
  Read sector buffer.
**/
void AtaDevice::readBuffer(u32int size, u8int *buf)
{

  u8int state = inb(commandBase+7);
  while (!(state & HD_STATUS_READY))
    state = inb(commandBase+7);

  while(size)
  {
    u16int sbuf[256];
    for (int i = 0; i < 256; i ++)
      sbuf[i] = inw(commandBase);

    if (size < 512)
    {
      memcpy(buf, sbuf, size);
      size = 0;
    }
    else
    {
      memcpy(buf, sbuf, 512);
      size -= 512;
      buf += 512;
    }
  }

//   u16int sh;
//   for (int i = size; i < size&0xFFFFFE00+256; i++)
//     sh = inw(commandBase);
}

void AtaDevice::clearCache()
{
  u8int state = inb(commandBase+7);
  while (state & HD_STATUS_BUFFER_NEEDS_SERVICING)
    u16int tmp = inw(commandBase);
}
